﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

using SlimDX;
using SlimDX.Direct3D9;

namespace DiverseMeshTools
{
    public static class D3D9MeshTools
    {
        #region VertexPositionNormalTexture
        [StructLayout(LayoutKind.Sequential)]
        private struct VertexPositionNormalTexture
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector2 Texture1;
        }
        #endregion

        #region VertexPositionNormalColor
        [StructLayout(LayoutKind.Sequential)]
        private struct VertexPositionNormalColor
        {
            public Vector3 Position;
            public Vector3 Normal;
            public int Color;
        }
        #endregion

        #region VertexPositionNormal
        [StructLayout(LayoutKind.Sequential)]
        private struct VertexPositionNormal
        {
            public Vector3 Position;
            public Vector3 Normal;
        }
        #endregion

        #region UniformVertex
        private struct UniformVertex : IComparable<UniformVertex>
        {
            public Vector3 Position;
            public Vector3 Normal;
            public int Color;
            public Vector2 Texture1;

            public int AttributeId;

            public string Key;

            public void UpdateKey(VertexFormat vertexFormat)
            {
                switch (vertexFormat)
                {
                    case VertexFormat.PositionNormal | VertexFormat.Texture1:
                        Key = AttributeId.ToString() + "|" + Position.ToString() + "|" + Normal.ToString() + "|" + Texture1.ToString();
                        break;

                    case VertexFormat.PositionNormal | VertexFormat.Diffuse:
                        Key = AttributeId.ToString() + "|" + Position.ToString() + "|" + Normal.ToString() + "|" + Color.ToString();
                        break;

                    case VertexFormat.PositionNormal:
                        Key = AttributeId.ToString() + "|" + Position.ToString() + "|" + Normal.ToString();
                        break;
                }
            }

            public static UniformVertex FromVertex(VertexPositionNormalTexture texturedVertex, bool updateKey)
            {
                UniformVertex uniformVertex = new UniformVertex();

                uniformVertex.Position = texturedVertex.Position;
                uniformVertex.Normal = texturedVertex.Normal;
                uniformVertex.Texture1 = texturedVertex.Texture1;
                if (updateKey) uniformVertex.UpdateKey(VertexFormat.PositionNormal | VertexFormat.Texture1);

                return uniformVertex;
            }

            public static UniformVertex FromVertex(VertexPositionNormalColor coloredVertex, bool updateKey)
            {
                UniformVertex uniformVertex = new UniformVertex();

                uniformVertex.Position = coloredVertex.Position;
                uniformVertex.Normal = coloredVertex.Normal;
                uniformVertex.Color = coloredVertex.Color;
                if (updateKey) uniformVertex.UpdateKey(VertexFormat.PositionNormal | VertexFormat.Diffuse);

                return uniformVertex;
            }

            public static UniformVertex FromVertex(VertexPositionNormal vertex, bool updateKey)
            {
                UniformVertex uniformVertex = new UniformVertex();

                uniformVertex.Position = vertex.Position;
                uniformVertex.Normal = vertex.Normal;
                if (updateKey) uniformVertex.UpdateKey(VertexFormat.PositionNormal);

                return uniformVertex;
            }

            public VertexPositionNormalTexture ToVertexPositionNormalTexture()
            {
                VertexPositionNormalTexture vertex = new VertexPositionNormalTexture();

                vertex.Position = Position;
                vertex.Normal = Normal;
                vertex.Texture1 = Texture1;

                return vertex;
            }

            public VertexPositionNormalColor ToVertexPositionNormalColor()
            {
                VertexPositionNormalColor vertex = new VertexPositionNormalColor();

                vertex.Position = Position;
                vertex.Normal = Normal;
                vertex.Color = Color;

                return vertex;
            }

            public VertexPositionNormal ToVertexPositionNormal()
            {
                VertexPositionNormal vertex = new VertexPositionNormal();

                vertex.Position = Position;
                vertex.Normal = Normal;

                return vertex;
            }

            #region IComparable<UniformVertex> Member

            public int CompareTo(UniformVertex other)
            {
                if (this.AttributeId > other.AttributeId)
                {
                    return 1;
                }
                else if (this.AttributeId < other.AttributeId)
                {
                    return -1;
                }
                else
                {
                    return 0;
                }
            }

            #endregion
        }
        #endregion

        #region Create Outline Mesh
        // I've started this project to create outlines around my celshaded meshes :).

        /// <summary>
        /// Creates an outline mesh for the specified mesh (VertexFormat: PositionNormal with or without Texture1 or Diffuse).
        /// </summary>
        /// <param name="mesh">Mesh to create from.</param>
        /// <param name="outlineWidth">Distance between mesh and mesh outline.</param>
        /// <param name="outlineColor">Color of the outline mesh.</param>
        /// <returns>Mesh outline.</returns>
        public static Mesh CreateOutline(Mesh mesh, float outlineWidth, int outlineColor)
        {
            if (mesh.VertexFormat == (VertexFormat.PositionNormal | VertexFormat.Texture1) ||
                mesh.VertexFormat == (VertexFormat.PositionNormal | VertexFormat.Diffuse) ||
                mesh.VertexFormat == (VertexFormat.PositionNormal))
            {
                DataStream dataStream;
                VertexPositionNormalColor[] colorPositionNormalArray = new VertexPositionNormalColor[mesh.VertexCount];

                #region IndexBuffer Arrays
                int indexCount = mesh.FaceCount * 3;

                UInt16[] inputIndex16Array = new UInt16[indexCount];
                UInt32[] inputIndex32Array = new UInt32[indexCount];

                UInt16[] outputIndex16Array = new UInt16[indexCount];
                UInt32[] outputIndex32Array = new UInt32[indexCount];
                #endregion

                Mesh outlineMesh = new Mesh(mesh.Device, mesh.FaceCount, mesh.VertexCount, mesh.CreationOptions, VertexFormat.Diffuse | VertexFormat.PositionNormal);

                #region Vertices
                switch (mesh.VertexFormat)
                {
                    #region VertexFormat.PositionNormal | VertexFormat.Texture1
                    case (VertexFormat.PositionNormal | VertexFormat.Texture1):

                        VertexPositionNormalTexture[] texturePositionNormalArray = new VertexPositionNormalTexture[mesh.VertexCount];

                        // Extract
                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormalTexture>(texturePositionNormalArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();

                        // Modify
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            colorPositionNormalArray[i].Position = texturePositionNormalArray[i].Position + texturePositionNormalArray[i].Normal * outlineWidth;
                            colorPositionNormalArray[i].Normal = Vector3.Negate(texturePositionNormalArray[i].Normal);
                            colorPositionNormalArray[i].Color = outlineColor;
                        }
                        break;
                    #endregion

                    #region VertexFormat.PositionNormal | VertexFormat.Diffuse
                    case (VertexFormat.PositionNormal | VertexFormat.Diffuse):
                        // Extract
                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormalColor>(colorPositionNormalArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();

                        // Modify
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            colorPositionNormalArray[i].Position += colorPositionNormalArray[i].Normal * outlineWidth;
                            colorPositionNormalArray[i].Normal = Vector3.Negate(colorPositionNormalArray[i].Normal);
                            colorPositionNormalArray[i].Color = outlineColor;
                        }
                        break;
                    #endregion

                    #region VertexFormat.PositionNormal
                    case (VertexFormat.PositionNormal):
                        
                        VertexPositionNormal[] positionNormalArray = new VertexPositionNormal[mesh.VertexCount];

                        // Extract
                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormal>(positionNormalArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();

                        // Modify
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            colorPositionNormalArray[i].Position = positionNormalArray[i].Position + positionNormalArray[i].Normal * outlineWidth;
                            colorPositionNormalArray[i].Normal = Vector3.Negate(positionNormalArray[i].Normal);
                            colorPositionNormalArray[i].Color = outlineColor;
                        }
                        break;
                    #endregion
                }

                // Write
                dataStream = outlineMesh.LockVertexBuffer(LockFlags.None);
                dataStream.WriteRange<VertexPositionNormalColor>(colorPositionNormalArray);
                outlineMesh.UnlockVertexBuffer();
                #endregion

                #region Indices
                if (mesh.IndexBuffer.Description.Format == Format.Index16)
                {
                    // Extract
                    dataStream = mesh.LockIndexBuffer(LockFlags.ReadOnly);
                    dataStream.ReadRange<UInt16>(inputIndex16Array, 0, indexCount);
                    mesh.UnlockIndexBuffer();

                    // Modify
                    for (int i = 0; i < indexCount; i++)
                    {
                        outputIndex16Array[indexCount - i - 1] = inputIndex16Array[i];
                    }

                    // Write
                    dataStream = outlineMesh.LockIndexBuffer(LockFlags.None);
                    dataStream.WriteRange<UInt16>(outputIndex16Array);
                    outlineMesh.UnlockIndexBuffer();
                }
                else
                {
                    // Extract
                    dataStream = mesh.LockIndexBuffer(LockFlags.ReadOnly);
                    dataStream.ReadRange<UInt32>(inputIndex32Array, 0, indexCount);
                    mesh.UnlockIndexBuffer();

                    // Modify
                    for (int i = 0; i < indexCount; i++)
                    {
                        outputIndex32Array[indexCount - i - 1] = inputIndex32Array[i];
                    }

                    // Write
                    dataStream = outlineMesh.LockIndexBuffer(LockFlags.None);
                    dataStream.WriteRange<UInt32>(outputIndex32Array);
                    outlineMesh.UnlockIndexBuffer();
                }
                #endregion

                return outlineMesh;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region NON-REALTIME
        // Blender exports way to much vertices ...
        public static Mesh Optimize(Mesh mesh)
        {
            if (mesh.VertexFormat == (VertexFormat.PositionNormal | VertexFormat.Texture1) ||
                mesh.VertexFormat == (VertexFormat.PositionNormal | VertexFormat.Diffuse) ||
                mesh.VertexFormat == (VertexFormat.PositionNormal))
            {
                AttributeRange[] attributes = mesh.GetAttributeTable();

                DataStream dataStream;
                Mesh newMesh = null;

                #region Extract index buffer.

                int indexCount = mesh.FaceCount * 3;

                UInt16[] indexBuffer16 = new UInt16[indexCount];
                UInt32[] indexBuffer32 = new UInt32[indexCount];

                dataStream = mesh.LockIndexBuffer(LockFlags.ReadOnly);

                if (mesh.IndexBuffer.Description.Format == Format.Index16)
                {
                    dataStream.ReadRange<UInt16>(indexBuffer16, 0, indexCount);
                }
                else
                {
                    dataStream.ReadRange<UInt32>(indexBuffer32, 0, indexCount);
                }

                mesh.UnlockIndexBuffer();


                int[] indexAttributeIds = new int[indexCount];

                for (int i = 0; i < attributes.Length; i++)
                {
                    for (int j = attributes[i].FaceStart * 3; j < attributes[i].FaceStart * 3 + attributes[i].FaceCount * 3; j += 3)
                    {
                        indexAttributeIds[j] = i;
                        indexAttributeIds[j + 1] = i;
                        indexAttributeIds[j + 2] = i;
                    }
                }
                #endregion

                #region Extract vertex buffer.
                UniformVertex[] originalVertexBuffer = new UniformVertex[mesh.VertexCount];
                UniformVertex[] vertexBuffer;

                switch (mesh.VertexFormat)
                {
                    case (VertexFormat.PositionNormal | VertexFormat.Texture1):
                        
                        VertexPositionNormalTexture[] texturedVertexArray = new VertexPositionNormalTexture[mesh.VertexCount];

                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormalTexture>(texturedVertexArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();

                        #region Convert to uniform vertex format and release texturedVertexArray memory.
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            originalVertexBuffer[i] = UniformVertex.FromVertex(texturedVertexArray[i], false);
                        }

                        #region Force release memory.
                        texturedVertexArray = null;
                        GC.Collect();
                        GC.WaitForPendingFinalizers();
                        #endregion
                        #endregion
                        break;

                    case (VertexFormat.PositionNormal | VertexFormat.Diffuse):

                        VertexPositionNormalColor[] coloredVertexArray = new VertexPositionNormalColor[mesh.VertexCount];

                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormalColor>(coloredVertexArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();

                        #region Convert to uniform vertex format and release coloredVertexArray memory.
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            originalVertexBuffer[i] = UniformVertex.FromVertex(coloredVertexArray[i], false);
                        }

                        #region Force release memory.
                        coloredVertexArray = null;
                        GC.Collect();
                        GC.WaitForPendingFinalizers();
                        #endregion
                        #endregion
                        break;

                    case (VertexFormat.PositionNormal):

                        VertexPositionNormal[] vertexArray = new VertexPositionNormal[mesh.VertexCount];

                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormal>(vertexArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();

                        #region Convert to uniform vertex format and release vertexArray memory.
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            originalVertexBuffer[i] = UniformVertex.FromVertex(vertexArray[i], false);
                        }

                        #region Force release memory.
                        vertexArray = null;
                        GC.Collect();
                        GC.WaitForPendingFinalizers();
                        #endregion
                        #endregion
                        break;
                }

                for (int i = 0; i < attributes.Length; i++)
                {
                    for (int j = attributes[i].VertexStart; j < attributes[i].VertexStart + attributes[i].VertexCount; j++)
                    {
                        originalVertexBuffer[j].AttributeId = i;
                        originalVertexBuffer[j].UpdateKey(mesh.VertexFormat);
                    }
                }
                #endregion

                DropDoubleVertices(ref originalVertexBuffer, mesh.VertexFormat, out vertexBuffer);

                #region Sort Vertices by AttributeId
                List<UniformVertex> vertexBufferList = new List<UniformVertex>(vertexBuffer);
                vertexBufferList.Sort();
                vertexBuffer = vertexBufferList.ToArray();

                vertexBufferList.Clear();
                vertexBufferList = null;
                #endregion

                #region Update index buffer and create new mesh.
                if (mesh.IndexBuffer.Description.Format == Format.Index16)
                {
                    UpdateIndex(ref indexBuffer16, ref indexAttributeIds, ref originalVertexBuffer, ref vertexBuffer);
                    newMesh = CreateMesh(mesh.Device, ref indexBuffer16, ref vertexBuffer, mesh.VertexFormat, attributes, mesh.GetMaterials(), mesh.GetEffects(), mesh.CreationOptions);
                }
                else
                {
                    UpdateIndex(ref indexBuffer32, ref indexAttributeIds, ref originalVertexBuffer, ref vertexBuffer);
                    newMesh = CreateMesh(mesh.Device, ref indexBuffer32, ref vertexBuffer, mesh.VertexFormat, attributes, mesh.GetMaterials(), mesh.GetEffects(), mesh.CreationOptions);
                }
                #endregion

                return newMesh;
            }
            else
            {
                return null;
            }
        }

        private static void DropDoubleVertices(ref UniformVertex[] vertexBufferIn, VertexFormat vertexFormat, out UniformVertex[] vertexBufferOut)
        {
            if (vertexFormat == (VertexFormat.PositionNormal | VertexFormat.Texture1) ||
                vertexFormat == (VertexFormat.PositionNormal | VertexFormat.Diffuse) ||
                vertexFormat == VertexFormat.PositionNormal)
            {
                Hashtable vertexTable = new Hashtable();
                object item = null;

                if (vertexBufferIn.Length > 0)
                {
                    // Add first item
                    vertexTable.Add(vertexBufferIn[0].Key, vertexBufferIn[0]);

                    // Add all the other vertices and drop doubles.
                    for (int i = 1; i < vertexBufferIn.Length; i++)
                    {
                        // Check if the vertex is already in the hashtable ...
                        item = vertexTable[vertexBufferIn[i].Key];

                        if (item == null)
                        {
                            // Add if it's not.
                            vertexTable.Add(vertexBufferIn[i].Key, vertexBufferIn[i]);
                        }
                    }
                }

                // Copy the unique vertices to the output array.
                vertexBufferOut = new UniformVertex[vertexTable.Count];

                IDictionaryEnumerator enumerator = vertexTable.GetEnumerator();
                int count = 0;

                while (enumerator.MoveNext())
                {
                    vertexBufferOut[count] = (UniformVertex)enumerator.Value;
                    count++;
                }

                vertexTable.Clear();
                vertexTable = null;
            }
            else
            {
                vertexBufferOut = null;
            }
        }

        /// <summary>
        /// Updates the index buffer to the new vertex array.
        /// </summary>
        /// <param name="indexBuffer">Original mesh index buffer or a copy to update.</param>
        /// <param name="originalVertexBuffer">Original mesh vertex buffer.</param>
        /// <param name="vertexBuffer">Vertex buffer with dropped double vertices.</param>
        private static void UpdateIndex(ref UInt16[] indexBuffer, ref int[] indexAttributeIds, ref UniformVertex[] originalVertexBuffer, ref UniformVertex[] vertexBuffer)
        {
            // Go through all indices ...
            for (int i = 0; i < indexBuffer.Length; i++)
            {
                // ... and find the first occurance of the original vertex in the new vertex buffer.
                for (int j = 0; j < vertexBuffer.Length; j++)
                {
                    if (originalVertexBuffer[indexBuffer[i]].Key == vertexBuffer[j].Key &&
                        originalVertexBuffer[indexBuffer[i]].AttributeId == indexAttributeIds[i])
                    {
                        indexBuffer[i] = (UInt16)j;
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Updates the index buffer to the new vertex array.
        /// </summary>
        /// <param name="indexBuffer">Original mesh index buffer or a copy to update.</param>
        /// <param name="originalVertexBuffer">Original mesh vertex buffer.</param>
        /// <param name="vertexBuffer">Vertex buffer with dropped double vertices.</param>
        private static void UpdateIndex(ref UInt32[] indexBuffer, ref int[] indexAttributeIds, ref UniformVertex[] originalVertexBuffer, ref UniformVertex[] vertexBuffer)
        {
            // Go through all indices ...
            for (int i = 0; i < indexBuffer.Length; i++)
            {
                // ... and find the first occurance of the original vertex in the new vertex buffer.
                for (int j = 0; j < vertexBuffer.Length; j++)
                {
                    if (originalVertexBuffer[indexBuffer[i]].Key == vertexBuffer[j].Key &&
                        originalVertexBuffer[indexBuffer[i]].AttributeId == indexAttributeIds[i])
                    {
                        indexBuffer[i] = (UInt32)j;
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Creates a new mesh instance.
        /// </summary>
        /// <param name="device">Device to create with.</param>
        /// <param name="indexBuffer">16 Bit index buffer.</param>
        /// <param name="vertexBuffer">Vertex buffer.</param>
        /// <param name="vertexFormat">Vertex format.</param>
        /// <param name="materials">Materials to use.</param>
        /// <param name="effects">Effects to use.</param>
        /// <param name="creationOptions">Flags to create with. MeshFlags.Use32Bit will be ignored.</param>
        /// <returns>A new mesh instance.</returns>
        private static Mesh CreateMesh(Device device, ref UInt16[] indexBuffer, ref UniformVertex[] vertexBuffer, VertexFormat vertexFormat, AttributeRange[] attributes, ExtendedMaterial[] materials, EffectInstance[] effects, MeshFlags creationOptions)
        {
            if (vertexFormat == (VertexFormat.PositionNormal | VertexFormat.Texture1) ||
                vertexFormat == (VertexFormat.PositionNormal | VertexFormat.Diffuse) ||
                vertexFormat == VertexFormat.PositionNormal)
            {
                if (indexBuffer.Length % 3 == 0)
                {
                    #region Remove MeshFlags.Use32Bit
                    if ((creationOptions & MeshFlags.Use32Bit) == MeshFlags.Use32Bit)
                    {
                        creationOptions = creationOptions ^ MeshFlags.Use32Bit;
                    }
                    #endregion

                    Mesh mesh = new Mesh(device, indexBuffer.Length / 3, vertexBuffer.Length, creationOptions, vertexFormat);

                    WriteVertexBuffer(ref mesh, ref vertexBuffer, vertexFormat);
                    WriteIndexBuffer(ref mesh, ref indexBuffer);

                    UpdateAttributeTable(ref attributes, ref vertexBuffer);

                    #region Set attributes, materials and effects. Generate adjacency. Optimize.
                    mesh.SetAttributeTable(attributes);
                    mesh.SetMaterials(materials);
                    mesh.SetEffects(effects);
                    mesh.GenerateAdjacency(0f);

                    mesh = mesh.Clean(CleanType.Optimization);
                    mesh.OptimizeInPlace(MeshOptimizeFlags.VertexCache | MeshOptimizeFlags.AttributeSort | MeshOptimizeFlags.Compact);
                    #endregion

                    return mesh;
                }
                else
                {
                    throw new Exception("Mesh index.Length isn't a multiple of three. Each face has three vertices.");
                }
            }
            else
            {
                throw new Exception("Unsupported vertex format. Supported formats: PositionNormal with or without Texture1 or Diffuse.");
            }
        }

        /// <summary>
        /// Creates a new mesh instance.
        /// </summary>
        /// <param name="device">Device to create with.</param>
        /// <param name="indexBuffer">32 Bit index buffer.</param>
        /// <param name="vertexBuffer">Vertex buffer.</param>
        /// <param name="vertexFormat">Vertex format.</param>
        /// <param name="materials">Materials to use.</param>
        /// <param name="effects">Effects to use.</param>
        /// <param name="creationOptions">Flags to create with. MeshFlags.Use32Bit will be forced.</param>
        /// <returns>A new mesh instance.</returns>
        private static Mesh CreateMesh(Device device, ref UInt32[] indexBuffer, ref UniformVertex[] vertexBuffer, VertexFormat vertexFormat, AttributeRange[] attributes, ExtendedMaterial[] materials, EffectInstance[] effects, MeshFlags creationOptions)
        {
            if (vertexFormat == (VertexFormat.PositionNormal | VertexFormat.Texture1) ||
                vertexFormat == (VertexFormat.PositionNormal | VertexFormat.Diffuse) ||
                vertexFormat == VertexFormat.PositionNormal)
            {
                if (indexBuffer.Length % 3 == 0)
                {
                    #region Add MeshFlags.Use32Bit
                    creationOptions = creationOptions | MeshFlags.Use32Bit;
                    #endregion

                    Mesh mesh = new Mesh(device, indexBuffer.Length / 3, vertexBuffer.Length, creationOptions, vertexFormat);

                    WriteVertexBuffer(ref mesh, ref vertexBuffer, vertexFormat);
                    WriteIndexBuffer(ref mesh, ref indexBuffer);

                    UpdateAttributeTable(ref attributes, ref vertexBuffer);

                    #region Set attributes, materials and effects. Generate adjacency. Optimize.
                    mesh.SetAttributeTable(attributes);
                    mesh.SetMaterials(materials);
                    mesh.SetEffects(effects);
                    mesh.GenerateAdjacency(0f);

                    mesh = mesh.Clean(CleanType.Optimization);
                    mesh.OptimizeInPlace(MeshOptimizeFlags.VertexCache | MeshOptimizeFlags.AttributeSort | MeshOptimizeFlags.Compact);
                    #endregion

                    return mesh;
                }
                else
                {
                    throw new Exception("Mesh index.Length isn't a multiple of three. Each face has three vertices.");
                }
            }
            else
            {
                throw new Exception("Unsupported vertex format. Supported formats: PositionNormal with or without Texture1 or Diffuse.");
            }
        }

        private static void UpdateAttributeTable(ref AttributeRange[] attributes, ref UniformVertex[] vertexBuffer)
        {
            int[] vertexCount = new int[attributes.Length];
            bool oneAdded;

            for (int id = 0; id < attributes.Length; id++)
            {
                vertexCount[id] = 0;
                oneAdded = false;

                for (int j = 0; j < vertexBuffer.Length; j++)
                {
                    if (vertexBuffer[j].AttributeId == id)
                    {
                        vertexCount[id]++;
                        oneAdded = true;
                    }

                    if (oneAdded)
                    {
                        if (vertexBuffer[j].AttributeId != id)
                        {
                            break;
                        }
                    }
                }
            }

            int vertices = 0;

            for (int id = 0; id < attributes.Length; id++)
            {
                attributes[id].VertexStart = vertices;
                attributes[id].VertexCount = vertexCount[id]; 
                vertices += vertexCount[id];
            }
        }

        private static void WriteVertexBuffer(ref Mesh mesh, ref UniformVertex[] vertexBuffer, VertexFormat vertexFormat)
        {
            DataStream dataStream;

            #region Write vertex buffer.
            switch (mesh.VertexFormat)
            {
                case (VertexFormat.PositionNormal | VertexFormat.Texture1):

                    VertexPositionNormalTexture[] texturedVertexArray = new VertexPositionNormalTexture[mesh.VertexCount];

                    for (int i = 0; i < vertexBuffer.Length; i++)
                    {
                        texturedVertexArray[i] = vertexBuffer[i].ToVertexPositionNormalTexture();
                    }

                    dataStream = mesh.LockVertexBuffer(LockFlags.Discard);
                    dataStream.WriteRange<VertexPositionNormalTexture>(texturedVertexArray);
                    mesh.UnlockVertexBuffer();
                    break;

                case (VertexFormat.PositionNormal | VertexFormat.Diffuse):

                    VertexPositionNormalColor[] coloredVertexArray = new VertexPositionNormalColor[mesh.VertexCount];

                    for (int i = 0; i < vertexBuffer.Length; i++)
                    {
                        coloredVertexArray[i] = vertexBuffer[i].ToVertexPositionNormalColor();
                    }

                    dataStream = mesh.LockVertexBuffer(LockFlags.Discard);
                    dataStream.WriteRange<VertexPositionNormalColor>(coloredVertexArray);
                    mesh.UnlockVertexBuffer();
                    break;

                case (VertexFormat.PositionNormal):

                    VertexPositionNormal[] vertexArray = new VertexPositionNormal[mesh.VertexCount];

                    for (int i = 0; i < vertexBuffer.Length; i++)
                    {
                        vertexArray[i] = vertexBuffer[i].ToVertexPositionNormal();
                    }

                    dataStream = mesh.LockVertexBuffer(LockFlags.Discard);
                    dataStream.WriteRange<VertexPositionNormal>(vertexArray);
                    mesh.UnlockVertexBuffer();
                    break;
            }
            #endregion
        }

        private static void WriteIndexBuffer(ref Mesh mesh, ref UInt16[] indexBuffer)
        {
            DataStream dataStream;

            dataStream = mesh.LockIndexBuffer(LockFlags.None);
            dataStream.WriteRange<UInt16>(indexBuffer);
            mesh.UnlockIndexBuffer();
        }

        private static void WriteIndexBuffer(ref Mesh mesh, ref UInt32[] indexBuffer)
        {
            DataStream dataStream;

            dataStream = mesh.LockIndexBuffer(LockFlags.None);
            dataStream.WriteRange<UInt32>(indexBuffer);
            mesh.UnlockIndexBuffer();
        }

        public static Mesh Simplify(Mesh mesh)
        {
            Mesh temp = mesh.Clean(CleanType.Simplification);
            return Mesh.Simplify(temp, (int)((float)mesh.FaceCount * 0.1f), MeshSimplification.Face);
        }

        public static Mesh Transform(Mesh mesh, Matrix transformation)
        {
            if (mesh.VertexFormat == (VertexFormat.PositionNormal | VertexFormat.Texture1) ||
                mesh.VertexFormat == (VertexFormat.PositionNormal | VertexFormat.Diffuse) ||
                mesh.VertexFormat == (VertexFormat.PositionNormal))
            {
                DataStream dataStream;
                Mesh newMesh = null;

                Hashtable tempVertexTable;
                string tempKey;
                object tempObject;

                IDictionaryEnumerator enumerator;
                int count;

                #region Extract Indices from Mesh

                int indexCount = mesh.FaceCount * 3;

                UInt16[] index16Array = new UInt16[indexCount];
                UInt32[] index32Array = new UInt32[indexCount];

                dataStream = mesh.LockIndexBuffer(LockFlags.ReadOnly);

                if (mesh.IndexBuffer.Description.Format == Format.Index16)
                {
                    dataStream.ReadRange<UInt16>(index16Array, 0, indexCount);
                }
                else
                {
                    dataStream.ReadRange<UInt32>(index32Array, 0, indexCount);
                }
                mesh.UnlockIndexBuffer();

                #endregion

                #region Vertices
                switch (mesh.VertexFormat)
                {
                    #region VertexFormat.PositionNormal | VertexFormat.Texture1
                    case (VertexFormat.PositionNormal | VertexFormat.Texture1):

                        #region Extract Vertices from Mesh
                        VertexPositionNormalTexture[] texturePositionNormalArray = new VertexPositionNormalTexture[mesh.VertexCount];
                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormalTexture>(texturePositionNormalArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();
                        #endregion

                        #region Modify Vertices
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            texturePositionNormalArray[i].Position = Vector3.TransformCoordinate(texturePositionNormalArray[i].Position, transformation);
                            texturePositionNormalArray[i].Normal = Vector3.TransformNormal(texturePositionNormalArray[i].Normal, transformation);
                        }
                        #endregion

                        #region Create New Mesh
                        newMesh = new Mesh(mesh.Device, mesh.FaceCount, texturePositionNormalArray.Length, mesh.CreationOptions, mesh.VertexFormat);

                        // Write
                        dataStream = newMesh.LockVertexBuffer(LockFlags.Discard);
                        dataStream.WriteRange<VertexPositionNormalTexture>(texturePositionNormalArray);
                        mesh.UnlockVertexBuffer();

                        // Write
                        dataStream = newMesh.LockIndexBuffer(LockFlags.None);

                        if (mesh.IndexBuffer.Description.Format == Format.Index16)
                        {
                            dataStream.WriteRange<UInt16>(index16Array);
                        }
                        else
                        {
                            dataStream.WriteRange<UInt32>(index32Array);
                        }

                        mesh.UnlockIndexBuffer();

                        newMesh.GenerateAdjacency(0f);
                        newMesh.SetMaterials(mesh.GetMaterials());
                        newMesh.SetEffects(mesh.GetEffects());

                        newMesh = newMesh.Clean(CleanType.Optimization);
                        newMesh.OptimizeInPlace(MeshOptimizeFlags.VertexCache | MeshOptimizeFlags.AttributeSort | MeshOptimizeFlags.Compact);
                        #endregion

                        break;
                    #endregion

                    #region VertexFormat.PositionNormal | VertexFormat.Diffuse
                    case (VertexFormat.PositionNormal | VertexFormat.Diffuse):

                        #region Extract Vertices from Mesh
                        VertexPositionNormalColor[] colorPositionNormalArray = new VertexPositionNormalColor[mesh.VertexCount];
                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormalColor>(colorPositionNormalArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();
                        #endregion

                        #region Modify Vertices
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            colorPositionNormalArray[i].Position = Vector3.TransformCoordinate(colorPositionNormalArray[i].Position, transformation);
                            colorPositionNormalArray[i].Normal = Vector3.TransformNormal(colorPositionNormalArray[i].Normal, transformation);
                        }
                        #endregion

                        #region Create New Mesh
                        newMesh = new Mesh(mesh.Device, mesh.FaceCount, colorPositionNormalArray.Length, mesh.CreationOptions, mesh.VertexFormat);

                        // Write
                        dataStream = newMesh.LockVertexBuffer(LockFlags.Discard);
                        dataStream.WriteRange<VertexPositionNormalColor>(colorPositionNormalArray);
                        mesh.UnlockVertexBuffer();

                        // Write
                        dataStream = newMesh.LockIndexBuffer(LockFlags.None);

                        if (mesh.IndexBuffer.Description.Format == Format.Index16)
                        {
                            dataStream.WriteRange<UInt16>(index16Array);
                        }
                        else
                        {
                            dataStream.WriteRange<UInt32>(index32Array);
                        }

                        mesh.UnlockIndexBuffer();

                        newMesh.GenerateAdjacency(0f);
                        newMesh.SetMaterials(mesh.GetMaterials());
                        newMesh.SetEffects(mesh.GetEffects());

                        newMesh = newMesh.Clean(CleanType.Optimization);
                        newMesh.OptimizeInPlace(MeshOptimizeFlags.VertexCache | MeshOptimizeFlags.AttributeSort | MeshOptimizeFlags.Compact);
                        #endregion

                        break;
                    #endregion

                    #region VertexFormat.PositionNormal
                    case (VertexFormat.PositionNormal):

                        #region Extract Vertices from Mesh
                        VertexPositionNormal[] positionNormalArray = new VertexPositionNormal[mesh.VertexCount];
                        dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
                        dataStream.ReadRange<VertexPositionNormal>(positionNormalArray, 0, mesh.VertexCount);
                        mesh.UnlockVertexBuffer();
                        #endregion

                        #region Modify Vertices
                        for (int i = 0; i < mesh.VertexCount; i++)
                        {
                            positionNormalArray[i].Position = Vector3.TransformCoordinate(positionNormalArray[i].Position, transformation);
                            positionNormalArray[i].Normal = Vector3.TransformNormal(positionNormalArray[i].Normal, transformation);
                        }
                        #endregion

                        #region Create New Mesh
                        newMesh = new Mesh(mesh.Device, mesh.FaceCount, positionNormalArray.Length, mesh.CreationOptions, mesh.VertexFormat);

                        // Write
                        dataStream = newMesh.LockVertexBuffer(LockFlags.Discard);
                        dataStream.WriteRange<VertexPositionNormal>(positionNormalArray);
                        mesh.UnlockVertexBuffer();

                        // Write
                        dataStream = newMesh.LockIndexBuffer(LockFlags.None);

                        if (mesh.IndexBuffer.Description.Format == Format.Index16)
                        {
                            dataStream.WriteRange<UInt16>(index16Array);
                        }
                        else
                        {
                            dataStream.WriteRange<UInt32>(index32Array);
                        }

                        mesh.UnlockIndexBuffer();

                        newMesh.GenerateAdjacency(0f);
                        newMesh.SetMaterials(mesh.GetMaterials());
                        newMesh.SetEffects(mesh.GetEffects());

                        newMesh = newMesh.Clean(CleanType.Optimization);
                        newMesh.OptimizeInPlace(MeshOptimizeFlags.VertexCache | MeshOptimizeFlags.AttributeSort | MeshOptimizeFlags.Compact);
                        #endregion

                        break;
                    #endregion
                }
                #endregion

                return newMesh;
            }
            else
            {
                return null;
            }
        }

        public static BoundingBox GetDimensions(Mesh mesh)
        {
            DataStream dataStream;
            Vector3[] vertices;

            dataStream = mesh.LockVertexBuffer(LockFlags.ReadOnly);
            vertices = D3DX.GetVectors(dataStream, mesh.VertexCount, mesh.VertexFormat);
            mesh.UnlockVertexBuffer(); 

            return BoundingBox.FromPoints(vertices);
        }
        #endregion
    }
}